MyBatis源码3

您所在的位置:网站首页 ora01036 在数据库能运行 MyBatis源码3

MyBatis源码3

2023-05-21 07:35| 来源: 网络整理| 查看: 265

MyBatis运行 操作数据库 Executor

前面分析了MyBatis从mapper.xml到sql解析一直到生成Mapper的代理实例,了解了MyBatis整个工作流程。接下来我们来看MyBatis与数据库的交互。

先来看一段代码:

private List selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }

这是DefaultSqlSession中执行查询的源码,此处我们可以看到在执行查询的时候是通过executor这个对象去操作的,在DefaultSqlSession中所有与数据库的操作都是通过这个executor对象操作的。

同时在mybatis-config.xml配置文件中也可以指定执行sql默认所用的Executor。

因此我们大概可以知道Executor就是MyBatis针对数据库操作的一层封装,或者说是对JDBC的Statement的封装。 每个SqlSession对应一个Executor。

从配置我们可以看出来,Executor有多种类型,它们分别是

public enum ExecutorType { SIMPLE, REUSE, BATCH }

对应的的Executor实现类:

2023-05-04-22-00-42-image.png

Executor类型

Executor从配置上面来看,可以配置为SIMPLE, REUSE, BATC,分别对应了SimpleExecutor,ReuseExecutor,BatchExecutor。

SimpleExecutor:简单执行器,也是MyBatis的默认执行器,每次执行一次数据操作(update,select)就开启一个Statement,用完就关闭。

ReuseExecutor:可重用的执行器,这里重用的是Statement,它会在一个Session作用域中,每次执行sql的时候会判断该sql是否已经执行过,每次执行过会将Statement进行缓存起来(一个Map),下次执行的时候直接重用上次的Statement。这种适用于开启一个会话要执行多次数据库操作,并且其中会带有重复的sql情况下使用。

BatchExecutor:批处理执行器,从名字就可以分辨出来,它会将多个SQL(一个会话),一次性执行。

以上三种Executor就是我们通常使用和可配置的Executor。

还有个特例独行的CachingExecutor。

CachingExecutor,自身不会有Executor的逻辑,它可以说是实际Executor的静态代理类,在原本的Executor基础上加了缓存功能,主要是针对查询,在执行sql前会查询一级缓存(Session级别的缓存)是否存在结果,如果存在则直接返回,否则就委派实际的Executor去执行,所委派的Executor就是上面的三种。

public class CachingExecutor implements Executor { private final Executor delegate; } Executor的创建

Executor的创建是由SqlSessionFactory调用Configuration#newExecutor创建的。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }

配置cacheEnabled默认为true,因此没有特殊配置的情况下都是开启了一级缓存了的,使用的是CachingExecutor,实际执行还是按照配置的ExecutorType创建的。

executor = (Executor) interceptorChain.pluginAll(executor); 拦截器,后面说。

StatementHandler

前面介绍了MyBatis中,真正与数据库交互的是通过Executor进行交互的,那么MyBatis是如何封装Executor,怎么用它操作数据库的呢?

接下来我们来看一段,Executor操作数据库的源码

org.apache.ibatis.executor.SimpleExecutor#doQuery

@Override public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 创建StatementHandler,StatementHandler用于创建JDBC的Statement,以及执行查询和返回 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 准备:java.sql.Statement,如对Statement设置参数等,设置超时参数等信息 stmt = prepareStatement(handler, ms.getStatementLog()); // 最后执行查询,返回其结果 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }

上面这一段是Executor查询的代码,update代码也类似:

@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } }

通过上面代码分析,可以总结一下Executor操作数据库的步骤:

创建StatementHandler 用创建的StatementHandler,创建JDBC的java.sql.Statement,并设置参数 执行JDBC的Statement,并返回其结果

由上分析,可以看出StatementHandler是一个非常重要的类。

StatementHandler的作用:StatementHandler主要用于创建及设置JDBC中java.sql.Statement的参数和执行Statement,并封装返回结果。

如果忘记了JDBC的Statement,看一段这个原生的JDBC代码,就能知道它主要在干啥了:

Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //加载驱动 Class.forName("com.mysql.jdbc.Driver"); //建立连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root"); //依据姓名查找学生信息 String studentname="lili"; String sql = "select * from student where studentname=?"; //创建PrepareStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, studentname); //执行SQL resultSet = preparedStatement.executeQuery(); //处理结果 while (resultSet.next()) { Student student = new Student(); int id = resultSet.getInt("studentid"); String name = resultSet.getString("studentname"); student.setStudentID(id); student.setStudentName(name); System.out.println(student); } } catch (Exception e) { e.printStackTrace(); //关闭资源 } finally { }

上面JDBC代码中的PreparedStatement就是java.sql.Statement的实现类。

StatementHandler接口:

public interface StatementHandler { // 准备Statement,也就是创建Statement,并设置其超时参数 Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException; // 对Statement设置sql中的占位参数 void parameterize(Statement statement) throws SQLException; // 批量操作 void batch(Statement statement) throws SQLException; // update操作,删除也是它 int update(Statement statement) throws SQLException; // 查询... List query(Statement statement, ResultHandler resultHandler) throws SQLException; Cursor queryCursor(Statement statement) throws SQLException; BoundSql getBoundSql(); ParameterHandler getParameterHandler(); }

StatementHandler体系:

2023-05-05-19-51-02-image.png

它的继承体系跟Executor类似,真正干活儿的就是BaseStatementHandler的实现类,RoutingStatementHandler是一个静态代理类,内部跟CachingExecutor一样,有个delegate属性,是BaseStatementHandler三个实现类其中一个。

着重介绍一下,这个RoutingStatement的作用,在Executor体系中CachingExecutor是一个静态代理Executor在Executor实现的基础上增加了缓存功能。这里的RoutingStatement功能类似,但是它实现的不是缓存,它实现的是一个路由选择功能:

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }

它会根据Statement类型,创建对应真正去操作这个Statement的Handler。

那么接下来你有可能要问了,这三种实现的的StatementHandler有什么区别呢?

SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句,也就是可以直接执行的sql语句。 PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句,需要参数设置等操作的sql语句,是一个预编译的sql。 CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。

这个StatementType是在解析mapper.xml生成MappedStatement的时候就决定了。

StatementHandler源码

接下来我们分析一下StatementHandler的源码,从而推出另外两个重要组件ParameterHandler、ResultSetHandler。

先来看StatementHandler的创建:

// org.apache.ibatis.executor.SimpleExecutor#doQuery StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建RoutingStatementHandler,RoutingStatement的创建逻辑在上面有介绍 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 拦截器,后面会介绍 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }

可以看到,实际对外工作的跟Executor类似,是这个代理对象RoutingStatementHandler。

(StatementHandler) interceptorChain.pluginAll(statementHandler);这里又出现了类似创建Executor的代码,还是拦截器功能实现,后面会详细讲。

StatementHandler准备:

// org.apache.ibatis.executor.SimpleExecutor#doQuery stmt = prepareStatement(handler, ms.getStatementLog()); private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); // 创建Statement stmt = handler.prepare(connection, transaction.getTimeout()); // 设置参数 handler.parameterize(stmt); return stmt; }

这一步,创建了Statement,并且对其参数进行了设置

最后执行返回:

// org.apache.ibatis.executor.SimpleExecutor#doQuery return handler.query(stmt, resultHandler);

步骤很简单,就分了三步,现在我们来考虑一下这三步结合我们写原生JDBC风封装,比较难的点在哪儿?

首先,肯定不是创建一个Statement,毕竟JDBC提供了API一下就创建好了,主要难点在:参数设置和返回结果封装。

那么MyBatis是如何做的呢?

在StatementHandler的功能实现类有个功能父类就是BaseStatementHandler,里面有两个重要属性:

2023-05-05-20-42-51-image.png

就是ResultSetHandler和ParameterHandler,一个对数据库操作结果进行封装成返回结果,一个是对数据库操作sql参数设置。

ParameterHandler

ParameterHandler没有上面的Executor和StatementHandler复杂

public interface ParameterHandler { // 获取参数对象 Object getParameterObject(); // 针对PreparedStatement设置参数对象 void setParameters(PreparedStatement ps) throws SQLException; }

它的功能比较单一,就是给PreparedStatement设置参数,类似于JDBC的:

//创建PrepareStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, studentname);

并且ParameterHandler也只有一个实现类

2023-05-05-21-05-39-image.png

它的创建跟Executor和StatementHandler一样,都是由Configuration对象创建

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // ..... this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); // .... } // org.apache.ibatis.session.Configuration#newParameterHandler public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }

mappedStatement.getLang():获取到的是LanguageDriver实例,在前面介绍解析MappedStatement的时候有说道对它的创建。

有注意到,parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);又是一段跟其它组件类似的,拦截器,照旧,后面会说。

另外着重说一下ParameterHandler的核心功能,设置参数

DefaultParameterHandler#setParameters

@Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { // 遍历ParameterMapping,这个ParameterMapping集合的生成在前面也有说到 for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { // 解析参数值 Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // 获取到TypeHandler,针对不同类型设置方式不同,TypeHandler有很多实现类 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }

设置参数主要步骤:

遍历 BoundSql的parameterMappings,对每个parameter设置属性。并从传来的参数解析出值。解析BoundSql中的parameterMappings的源码在:SqlSourceBuilder#parse => 对ParameterMappingTokenHandler; 针对每个ParameterMapping获取到对应的TypeHandler 调用TypeHandler设置参数值 ResultSetHandler

前面分析了ParameterHandler,对sql参数设置,接下来我们分析对数据库操作结果进行封装,将会使用到ResultSetHandler。

public interface ResultSetHandler { // 处理结果集,用得最多的 List handleResultSets(Statement stmt) throws SQLException; // 批量处理结果集 Cursor handleCursorResultSets(Statement stmt) throws SQLException; // 处理存储过程结果 void handleOutputParameters(CallableStatement cs) throws SQLException; }

2023-05-05-21-53-07-image.png

默认,也仅且一个实现类就是DefaultResultSetHandler。

跟其它的一样,创建ResultSetHandler也是由Configuration完成的

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // ..... this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); // .... } // org.apache.ibatis.session.Configuration#newResultSetHandler public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }

创建逻辑比较简单,我们又看到了熟悉的身影 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 拦截器,还是放到后面统一说。

接下来我们看它的核心方法:

@Override public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List multipleResults = new ArrayList(); int resultSetCount = 0; // 获取第一个结果集,封装成ResultSetWrapper ResultSetWrapper rsw = getFirstResultSet(stmt); List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // 持续遍历结果,并处理:java.sql.ResultSet。将其封装到multipleResults集合中 while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); }

在对返回结果封装入口代码就是:handleResultSet(rsw, resultMap, multipleResults, null);

ResultSetHandler是通过反射创建的返回对象,源码是DefaultResultSetHandler#createResultObject(....)。

MyBatis操作数据库总结

2023-05-07-10-28-27-image.png

上面这个图,描述了Executor在操作数据库的时候和JDBC操作数据库的对应关系步骤,我们可以看到Executor实际底层也是使用JDBC,只不过在操作Statement以及参数设置,查询返回等关键步骤封装成了组件,也就是:StatementHandler、ParameterHandler、ResultSetHandler,网上有把它们与Executor组件统称为”MyBatis四大组件“,“SqlSession四大对象“....。

Executor:负责发起操作数据库sql请求,初始化其它三大组件。

StatementHandler:语句处理器,负责和JDBC交互,包括创建JDBC的Statement,处理prepare、执行语句,调用ParameterHandler设置参数,调用ResultSetHandler封装返回结果。

ParameterHandler:参数处理器,负责处理预编译sql的入参,依赖ParameterMapping。

ResultSetHandler:结果处理器,负责将数据库操作结果映射成Java返回结果。

我们在追溯源码的时候,可以看到这四个组件,都在创建的时候,执行了一个类似于这样的语句:interceptorChain.pluginAll(resultSetHandler); 这是MyBatis提供的拦截器,方便编码人员能够随时干预数据库执行过程中的步骤,如参数改写,sql改写,返回结果再封装等操作,MyBatis并没有向Spring框架一样,提供回调接口的方式来进行扩展而是采用的拦截器,这种方式,扩展性更强一点,也更灵活,但是对新手也不太友好,因为需要了解到四大组件的功能具体API功能才能很好应用。

](blog.51cto.com/u_12393361/…)

MyBatis执行流程总结

至此就分析完了整个MyBatis运行阶段,从sql生成到sql执行的整体流程,接下来统一做个小总结

在启动的时候通过获取要扫描的mapper.xml或者注解,进行将sql片段解析为SqlNode对象,生成MappedStatement对象,放入Configuration中。 在运行过程中获取到Mapper接口实例,通过SqlSession#getMapper方法为入口,创建Mapper接口的代理实例,代理对象方法的执行者为MapperProxy。 调用Mapper接口实例的时候,实际执行逻辑是MapperProxy的invoke方法,该方法会通过启动时的MappedStatement以及执行过程中Mapper接口方法来进行判断当前执行方法是什么样的类型,从而调用SqlSession的方法,执行sql语句。 SqlSession执行sql前,会通过第一步解析好的MappedStatement,从而根据参数解析出预编译sql,以及参数信息。 通过Executor执行sql语句。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3